#include "HID_ISP.h"
#include "prog.h"
#include "EmAES.h"
/**
  ROM program related API's
 */
/**
these value will be program in the PARAM_BASE later
const DevParam_t devParam_impl __attribute__((at(PARAM_BASE))) = 
{
	VALID_FLAG,
	{'L','D','-','d','e','f','a','u','l','t'},
	{  
  0x02, 0x50, 0x02, 0x50, 0x02, 0x50, 0x02, 0x50,
  0x02, 0x50, 0x02, 0x50, 0x02, 0x50, 0x02, 0x50,
  0x02, 0x50, 0x02, 0x50, 0x02, 0x50, 0x02, 0x50,
  0x02, 0x50, 0x02, 0x50, 0x02, 0x50, 0x02, 0x50
    },
};
*/

typedef struct tagInputRawData
{
	uint32_t   address;
	uint8_t	   data[60];
}InputRawData_t,*PInputRawData_t;

uint32_t    pageUsage[(MAX_FLASH_SIZE/PAGE_SIZE)/32];
uint32_t    progLength = 0;							// length in words
uint32_t    crcValue;								// data crc value
//uint32_t    blockCount;
uint32_t    startAddress;
ProgState 	progState = PS_Unknown;

/*
void   InitialROMProgram(uint32_t crc, uint32_t length, uint32_t blockCnt)
{
	uint32_t i;
	crcValue = crc;
	for(i=0;i<sizeof(pageUsage)/sizeof(pageUsage[0]);i++){
		pageUsage[i] = 0;
	}
	progLength = length;
	blockCount = blockCnt;
}
*/
uint32_t ISPRead(uint32_t addr)
{
	outp32(FMC_ISPCMD, 0x00);
	outp32(FMC_ISPADR, addr);
	outp32(FMC_ISPTRG, 0x01);
	__ISB();
	return inp32(FMC_ISPDAT);
}

void ISPProg(uint32_t addr, uint32_t data)
{
	outp32(FMC_ISPCMD, 0x21);
	outp32(FMC_ISPADR, addr);
	outp32(FMC_ISPDAT, data);
	outp32(FMC_ISPTRG, 0x01);
	__ISB();
	FMC->ISPCON.ISPFF = 1;
}

void  ProgramROM(uint32_t address, const uint32_t* buf, uint32_t size)
{
	uint32_t row = (address-startAddress)/PAGE_SIZE;
	uint32_t bits = 1<<(row&31);
	//if(address & 0x80000000) return;
	/*	there is no need to check the address, ISP will do it
	if(g_u32FlashBaseAddress == APROM_BASE){
		// Program in APROM
		if( (address < g_u32FlashBaseAddress) 
		||  ((address+size) > (g_u32FlashBaseAddress + g_u32FlashSize))){
			// address out of range, do nothing here
			return;
		}
	}else{
		// Program in LDROM
		if( (address+size) > g_u32FlashSize ){

			return;
		}
	}*/
	row /= 32;
	if(row >= sizeof(pageUsage)/sizeof(pageUsage[0])){
		return;
	}
	if( !(pageUsage[row] & bits) ){
		//  Page not used, erase it before program in it
		pageUsage[row] |= bits;
		outp32(FMC_ISPCMD, 0x22);
    	outp32(FMC_ISPADR, address & PAGE_MASK);
    	outp32(FMC_ISPTRG, 0x01);
		// wait until operation done
		//while (FMC->ISPTRG.ISPGO);
		__ISB();			 
		//if(FMC->ISPCON.ISPFF){
			FMC->ISPCON.ISPFF = 1;
		//}
	}
	while(size){
		ISPProg(address,*buf);
		buf++;
		address+=4;
		size--;
		//__ISB();
		//if(FMC->ISPCON.ISPFF){
			FMC->ISPCON.ISPFF = 1;
		//}
	}

}

extern unsigned long Crc32Table[256];
uint32_t  CheckROM(void)
{
	uint32_t size = progLength;
	uint32_t addr = startAddress;
	uint32_t crc = 0xFFFFFFFF;
	MakeCrc32Table();
	while(size){
		uint32_t data = ISPRead(addr);
		addr += 4;
		size -= 4;
		crc = (crc << 8) ^ Crc32Table[ (((crc >> (32-8)) & 0xFF) ^ data) & 0xFF];
		data>>=8;
		crc = (crc << 8) ^ Crc32Table[ (((crc >> (32-8)) & 0xFF) ^ data) & 0xFF];
		data>>=8;
		crc = (crc << 8) ^ Crc32Table[ (((crc >> (32-8)) & 0xFF) ^ data) & 0xFF];
		data>>=8;
		crc = (crc << 8) ^ Crc32Table[ (((crc >> (32-8)) & 0xFF) ^ data) & 0xFF]; 
	}
	crc ^= 0xFFFFFFFF;
	return crc == crcValue ? 1 : 0;
}
/*
void   OnInitialProg(void)
{
    progState = PS_Initial;
	_DRVUSB_TRIG_EP(EP_INT_OUT, 64);
}

void   OnConfigProg(void)
{ 
	progState = PS_Config;
	_DRVUSB_TRIG_EP(EP_INT_OUT, 64);
}
 */
uint8_t  cipher[16];
void   OnRecvData(uint32_t* data)
{
    //uint32_t rawData[16];
	//my_memcpy(rawData,data,64);
	uint32_t* rawData = data;
    switch(progState)
	{
		case PS_Initial:
		{
			PInitBlock_t p = (PInitBlock_t)rawData;
			uint32_t i;
			if(devParam->flag == VALID_FLAG){
				my_memcpy(kTable,devParam->key,sizeof(devParam->key));
			}
			aesDecInit();
			my_memcpy(cipher,p->cipher,16);
			aesDecrypt((unsigned char*)(rawData+4),cipher);
			aesDecrypt((unsigned char*)(rawData+8),cipher);
			aesDecrypt((unsigned char*)(rawData+12),cipher);

			crcValue = p->crc;
			for(i=0;i<sizeof(pageUsage)/sizeof(pageUsage[0]);i++){
				pageUsage[i] = 0;
			}
			progLength = p->length;
			//blockCount = p->blockCount;
			startAddress = p->startAddress;
			//InitialROMProgram(p->crc,p->length,p->blockCount);
			progState = PS_Update;

			outp32(FMC_ISPCMD, 0x22);
    		outp32(FMC_ISPADR, APP_FLAG_ADDR & PAGE_MASK);
    		outp32(FMC_ISPTRG, 0x01);
			__ISB();
			break;	 
		}
		case PS_Update:
		{
			uint32_t pageRemain;
			PProgBlock_t p = (PProgBlock_t)rawData;
			aesDecrypt((unsigned char*)(rawData),cipher);
			aesDecrypt((unsigned char*)(rawData+4),cipher);
			aesDecrypt((unsigned char*)(rawData+8),cipher);
			aesDecrypt((unsigned char*)(rawData+12),cipher);
			if(p->addr == 0xFFFFFFFF){
				// the last block
				uint32_t res;
				for(res=0;res<16;res++){
				    rawData[res] = 0;
				}
				rawData[0] = CheckROM();// | blockCount<<8;
				if(rawData[0] == 1){
					ISPProg(APP_FLAG_ADDR & PAGE_MASK,APP_VALID);
				}
				// Send data, and set status to pending
				SendData((uint8_t*)rawData);
				break;
			}
			pageRemain = ((p->addr & PAGE_MASK)+ PAGE_SIZE) - p->addr;
			if(  pageRemain < 60){
				pageRemain>>=2;
				ProgramROM(p->addr,p->data,pageRemain);
				ProgramROM(p->addr+pageRemain*4,p->data+pageRemain,15-pageRemain);
			}else{
				ProgramROM(p->addr,p->data,60>>2);
			}
			//blockCount--;
			break;
		}
		case PS_Config:
		{
			PConfigProg_t p = (PConfigProg_t)rawData;
			if(p->bRead == 0){
				FMC->ISPCON.CFGUEN = 1;
				// Erase config words
				outp32(FMC_ISPCMD, 0x22);
		    	outp32(FMC_ISPADR, CFG0_ADDR);
		    	outp32(FMC_ISPTRG, 0x01);
				__ISB();
				ISPProg(CFG0_ADDR,p->config0);
				ISPProg(CFG1_ADDR,p->config1);
			}
			p->config0 = ISPRead(CFG0_ADDR);
			p->config1 = ISPRead(CFG1_ADDR);
			// Send data, and set status to pending
			SendData((uint8_t*)rawData);
			break;
		}
		case PS_Reset:
		{
			SetUserReset();
			outp32(&SYS->IPRSTC1, 0x02);
			while(1);
		}
		default:
		break;
	}
}



